package xyz.eden.gateway.springcloud.admin.plugin.security.auth;

import com.alibaba.fastjson2.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import xyz.eden.gateway.springcloud.admin.plugin.constant.GatewayConstant;
import xyz.eden.gateway.springcloud.admin.plugin.constant.ResponseConstant;
import xyz.eden.gateway.springcloud.admin.proxy.permission.AuthDetectionReq;
import xyz.eden.gateway.springcloud.admin.proxy.ResultSet;
import xyz.eden.gateway.springcloud.admin.proxy.permission.PermissionProxy;
import xyz.eden.gateway.springcloud.admin.util.ApplicationContextUtil;
import xyz.eden.gateway.springcloud.admin.util.AuthSchemaUtil;
import xyz.eden.gateway.springcloud.admin.util.HttpMethodUtil;
import xyz.eden.gateway.springcloud.admin.util.RequestUtils;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;

@Slf4j
@Component
public class UrlAuthDetectionGatewayFilterFactory extends AbstractGatewayFilterFactory<UrlAuthDetectionGatewayFilterFactory.Config> {

    private static final String PLUGIN_NAME = "UrlAuthDetection";
    protected UrlAuthDetectionGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public String name() {
        return PLUGIN_NAME;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();

                Route route = (Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                String requestUrl = request.getPath().value();
                String path = request.getURI().getPath();
                HttpHeaders headers = request.getHeaders();

                /**
                 * 如果 open 为 false, 则直接同通过
                 */
                if (!Objects.equals(config.getOpen(), true)) {
                    log.info("Config: [{}] open: [false], 不进行鉴权.", JSON.toJSONString(config));
                    return chain.filter(exchange);
                }


                /**
                 * 如果不需要登录, 则不需要鉴权
                 */
                String loginConstraintTag = headers.getFirst(GatewayConstant.LoginPluginValKey.LOGIN_CONSTRAINT_TAG);
                if (StringUtils.isBlank(loginConstraintTag) || !Objects.equals(loginConstraintTag, "true")) {
                    log.info("url: [{}], 请求头中没有登录限制标记, 不进行鉴权.", requestUrl);
                    return chain.filter(exchange);
                }


                /**
                 * 组装参数鉴权
                 */
                String employeeNo = headers.getFirst("employeeNo");
                AuthDetectionReq authDetectionRequest = new AuthDetectionReq();
                authDetectionRequest.setServiceId(route.getId());
                authDetectionRequest.setMethodCode(HttpMethodUtil.builderMethod(request.getMethod()));
                authDetectionRequest.setEmployeeNo(employeeNo);
                authDetectionRequest.setUri(requestUrl);
                authDetectionRequest.setSchemaCode(AuthSchemaUtil.getCode(config.getAuthSchema()));
                // API
                authDetectionRequest.setResourceTypeCode(4);

                ResultSet<Boolean> resultWrapper = doAuthDetection(authDetectionRequest);
                boolean detectionResult = handleAuthDetectionResult(resultWrapper);
                if (!detectionResult) {
                    ResultSet<Boolean> resultSet = ResultSet.<Boolean>builder()
                            .code(ResponseConstant.AuthDetectionResult.NO_AUTH).msg("网关鉴权-无权限")
                            .build();
                    return RequestUtils.unauthorized(exchange.getResponse(), resultSet);
                }
                return chain.filter(exchange);
            }
        };
    }

    private ResultSet<Boolean> doAuthDetection(AuthDetectionReq req) {
        PermissionProxy permissionProxy = ApplicationContextUtil.getBean(PermissionProxy.class);
        CompletableFuture<ResultSet<Boolean>> completableFuture = CompletableFuture.supplyAsync(() -> {
            return permissionProxy.authDetection(req);
        });
        ResultSet<Boolean> resultSet;
        try {
            resultSet = completableFuture.get();
        } catch (Exception e) {
            log.warn("doAuthDetection Exception, reqInfo: [{}], msg: [{}].", JSON.toJSONString(req), e.getMessage(), e);
            resultSet = ResultSet.<Boolean>builder()
                    .code(ResponseConstant.AuthDetectionResult.AUTH_DETECTION_EXCEPTION).msg("鉴权失败")
                    .build();
        }
        return resultSet;
    }

    private boolean handleAuthDetectionResult(ResultSet<Boolean> resultSet) {
        if (Objects.isNull(resultSet)) {
            log.info("handleAuthDetectionResult - resultSet 为空");
            return false;
        }
        if (!Objects.equals(resultSet.getCode(), 0)) {
            log.info("handleAuthDetectionResult - resultWrapper: [{}], code 非 0.", JSON.toJSONString(resultSet));
            return false;
        }
        return resultSet.getData();
    }


    /**
     * 配置
     */
    @Data
    public static class Config {

        /**
         * 是否开启
         */
        private Boolean open;
        /**
         * 权限模式
         */
        private String authSchema;
    }
}
